Création d'un composant
Créer un composant "Symfony UX" pour Quiz interactif
Notions théoriques
Qu'est-ce qu'un composant Twig dans Symfony UX ?
Symfony UX propose 2 types de composants réutilisables côté template : les Twig Components et les Live Components.
Dans ce cours, on travaille avec les Twig Components, qui constituent la base de tout composant Symfony UX personnalisé.
Un Twig Component est une classe PHP associée à un template Twig. Ensemble, ils forment une unité autonome et réutilisable que l'on peut appeler depuis n'importe quel template de l'application, comme une balise HTML personnalisée.
L'idée est simple : plutôt que de copier-coller du HTML et de la logique PHP dans plusieurs templates, on encapsule tout dans un composant.
On l'appelle ensuite avec une syntaxe concise :
<twig:Quiz :questions="questions" />
Cette ligne suffit à afficher un quiz complet, avec toute sa logique et son rendu visuel.
Les 2 packages nécessaires
Pour créer des composants Twig dans Symfony UX, il faut installer deux packages :
symfony/ux-twig-component: fournit la classe de baseAbstractComponentet le système de rendu des composants Twigsymfony/ux-stimulus-bundle: fournit Stimulus pour gérer les interactions JavaScript (déjà installé avec--webapp)
composer require symfony/ux-twig-component
Si le projet a été créé avec symfony new mon-projet --webapp, le bundle Stimulus est déjà installé. Seul ux-twig-component peut manquer.
La structure d'un Twig Component
Un composant Twig se compose de deux fichiers :
1. La classe PHP — placée dans src/Twig/Components/
// src/Twig/Components/Alert.php
namespace App\Twig\Components;
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
#[AsTwigComponent]
class Alert
{
public string $type = 'info';
public string $message = '';
}
2. Le template Twig — placé dans templates/components/
{# templates/components/Alert.html.twig #}
<div class="alert alert-{{ type }}">
{{ message }}
</div>
La convention de nommage est stricte : si la classe s'appelle Alert, le template doit s'appeler Alert.html.twig dans templates/components/. Symfony UX fait le lien automatiquement grâce à l'attribut #[AsTwigComponent].
L'attribut PHP #[AsTwigComponent]
L'attribut #[AsTwigComponent] (introduit en PHP 8.0) est ce qui transforme une classe PHP ordinaire en composant Twig. Il indique à Symfony UX que cette classe est un composant et lui permet de :
- Détecter automatiquement le template associé
- Exposer les propriétés publiques de la classe comme variables Twig
- Permettre l'appel du composant depuis un template avec
<twig:NomDuComposant />
On peut aussi personnaliser le nom du composant :
#[AsTwigComponent('mon-quiz')]
class Quiz { ... }
Ce qui permettrait de l'appeler avec <twig:mon-quiz />.
Les propriétés publiques : le pont PHP/Twig
Les propriétés publiques de la classe PHP sont automatiquement accessibles dans le template Twig du composant. C'est le mécanisme central de la communication entre la logique PHP et le rendu HTML.
#[AsTwigComponent]
class Quiz
{
public string $titre = 'Quiz sans titre';
public array $questions = [];
public bool $afficherScore = true;
}
Dans le template, on accède directement à titre, questions et afficherScore sans déclaration supplémentaire.
Seules les propriétés publiques sont exposées au template. Les propriétés protected et private restent inaccessibles depuis Twig — elles peuvent servir pour la logique interne de la classe.
Passer des données au composant
Lorsqu'on appelle un composant depuis un template parent, on lui passe des données via des attributs HTML-like :
{# Appel avec des valeurs statiques #}
<twig:Alert type="danger" message="Une erreur est survenue" />
{# Appel avec des variables Twig (préfixe : pour évaluer l'expression) #}
<twig:Alert :type="alertType" :message="alertMessage" />
Le préfixe : avant un attribut indique à Symfony UX d'évaluer l'expression Twig passée en valeur, plutôt que de la traiter comme une chaîne de caractères.
Sans le préfixe :, la valeur est toujours interprétée comme une chaîne de caractères. type="danger" passe la chaîne "danger", tandis que :type="monType" passe la valeur de la variable Twig monType.
Les méthodes dans un composant
Une classe de composant peut contenir des méthodes publiques, accessibles depuis le template :
#[AsTwigComponent]
class Quiz
{
public array $questions = [];
public function getNombreQuestions(): int
{
return count($this->questions);
}
}
Dans le template :
<p>Ce quiz contient {{ this.getNombreQuestions() }} question(s).</p>
Dans le template d'un composant, this désigne l'instance de la classe PHP du composant. On accède aux méthodes avec this.nomDeLaMethode() et aux propriétés avec nomDeLaPropriete.
Ajouter de l'interactivité avec Stimulus
Un Twig Component est statique : il génère du HTML côté serveur, sans interaction côté client. Pour ajouter de l'interactivité (comme révéler la bonne réponse au clic), on utilise Stimulus en ajoutant des attributs data-controller et data-action dans le template du composant.
<div data-controller="quiz">
<button data-action="click->quiz#verifier">Vérifier ma réponse</button>
<p data-quiz-target="resultat"></p>
</div>
Et dans un contrôleur Stimulus créé dans assets/controllers/quiz_controller.js :
import { Controller } from '@hotwired/stimulus';
export default class extends Controller {
static targets = ['resultat'];
verifier() {
this.resultatTarget.textContent = 'Bonne réponse !';
}
}
Cette combinaison Twig Component (logique PHP + rendu HTML) + Stimulus (interactivité JS) est le cœur de Symfony UX. Le PHP gère les données et la structure, JavaScript gère uniquement ce qui ne peut pas se faire côté serveur.
Exemple pratique
Création d'un composant Quiz complet
Cet exemple crée un composant Quiz réutilisable qui affiche une série de questions à choix multiple et révèle la bonne réponse au clic, grâce à Stimulus.
Étape 1 — Installer ux-twig-component
composer require symfony/ux-twig-component
Étape 2 — Créer la classe PHP du composant
Créer le fichier src/Twig/Components/Quiz.php :
<?php
namespace App\Twig\Components;
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
#[AsTwigComponent]
class Quiz
{
public string $titre = 'Quiz';
/** @var array<array{question: string, options: string[], answer: string}> */
public array $questions = [];
public function getNombreQuestions(): int
{
return count($this->questions);
}
}
Étape 3 — Créer le template Twig du composant
Créer le fichier templates/components/Quiz.html.twig :
<div class="quiz-wrapper" data-controller="quiz">
<h2>{{ titre }}</h2>
<p><em>{{ this.getNombreQuestions() }} question(s)</em></p>
{% for index, q in questions %}
<div class="quiz-question" data-quiz-target="question" style="margin-bottom: 1.5rem; padding: 1rem; border: 1px solid #ddd; border-radius: 6px;">
<p><strong>{{ index + 1 }}. {{ q.question }}</strong></p>
{% for option in q.options %}
<label style="display: block; margin: 0.3rem 0; cursor: pointer;">
<input
type="radio"
name="question_{{ index }}"
value="{{ option }}"
data-answer="{{ q.answer }}"
data-action="change->quiz#selectionner"
data-quiz-target="option"
>
{{ option }}
</label>
{% endfor %}
<p data-quiz-target="feedback{{ index }}" style="margin-top: 0.5rem; font-weight: bold;"></p>
</div>
{% endfor %}
</div>
Étape 4 — Créer le contrôleur Stimulus
Créer le fichier assets/controllers/quiz_controller.js :
import { Controller } from '@hotwired/stimulus';
export default class extends Controller {
selectionner(event) {
const input = event.target;
const reponseChoisie = input.value;
const bonnereponse = input.dataset.answer;
// Trouver l'index de la question via le nom du champ radio
const nom = input.name; // ex: "question_0"
const index = nom.split('_')[1];
// Trouver le paragraphe feedback correspondant
const feedbackTarget = this.element.querySelector(
`[data-quiz-target="feedback${index}"]`
);
if (!feedbackTarget) return;
if (reponseChoisie === bonnereponse) {
feedbackTarget.textContent = '✓ Bonne réponse !';
feedbackTarget.style.color = 'green';
} else {
feedbackTarget.textContent = `✗ Mauvaise réponse. La bonne réponse était : ${bonnereponse}`;
feedbackTarget.style.color = 'red';
}
// Désactiver tous les boutons radio de cette question
const radios = this.element.querySelectorAll(`input[name="${nom}"]`);
radios.forEach(r => r.disabled = true);
}
}
Étape 5 — Utiliser le composant dans un contrôleur Symfony
Créer un contrôleur de test :
php bin/console make:controller QuizDemoController
Modifier src/Controller/QuizDemoController.php :
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class QuizDemoController extends AbstractController
{
#[Route('/quiz-demo', name: 'app_quiz_demo')]
public function index(): Response
{
$questions = [
[
'question' => 'Quel attribut PHP active un Twig Component ?',
'options' => ['#[Component]', '#[AsTwigComponent]', '#[TwigComponent]', '#[UxComponent]'],
'answer' => '#[AsTwigComponent]',
],
[
'question' => 'Où place-t-on les classes PHP des Twig Components ?',
'options' => ['src/Components/', 'src/Twig/Components/', 'src/UX/', 'templates/components/'],
'answer' => 'src/Twig/Components/',
],
[
'question' => 'Quel préfixe évalue une expression Twig dans un attribut de composant ?',
'options' => ['@', '$', '!', ':'],
'answer' => ':',
],
];
return $this->render('quiz_demo/index.html.twig', [
'questions' => $questions,
]);
}
}
Étape 6 — Appeler le composant dans le template
Modifier templates/quiz_demo/index.html.twig :
{% extends 'base.html.twig' %}
{% block title %}Démo Quiz{% endblock %}
{% block body %}
<div style="max-width: 700px; margin: 2rem auto; padding: 0 1rem;">
<h1>Démonstration du composant Quiz</h1>
<twig:Quiz
titre="Quiz Symfony UX"
:questions="questions"
/>
</div>
{% endblock %}
Étape 7 — Tester
symfony server:start
Ouvrir https://127.0.0.1:8000/quiz-demo. Chaque question affiche ses options sous forme de boutons radio. Au clic sur une option, le feedback s'affiche immédiatement en vert ou en rouge, et les options sont désactivées pour empêcher un second choix.
Le composant est entièrement réutilisable. Pour afficher un second quiz sur la même page, il suffit d'appeler <twig:Quiz titre="Quiz 2" :questions="autresQuestions" /> avec un autre tableau de questions.
Test de mémorisation/compréhension
TP "Quiz Symfony UX"
Dans ce TP, vous allez créer de A à Z un composant Twig r éutilisable nommé Alerte, capable d'afficher des messages de différents types (succès, erreur, avertissement, info), puis vous l'utiliserez dans une page de démonstration. Vous ajouterez ensuite un comportement de fermeture via un contrôleur Stimulus.
Étape 1 — Créer le projet et installer les dépendances
Ouvrez un terminal PowerShell ou le terminal intégré de VS Code (Ctrl + ù), naviguez vers votre dossier Documents et créez un nouveau projet Symfony :
cd %USERPROFILE%\Documents
symfony new tp-twig-component --webapp
cd tp-twig-component
code .
Une fois VS Code ouvert, installez le package ux-twig-component :
composer require symfony/ux-twig-component
Vérifiez que l'installation s'est bien passée en cherchant symfony/ux-twig-component dans le fichier composer.json à la racine du projet.
Une solution
Vous devez être connecté pour voir le contenu.
Étape 2 — Créer la classe PHP du composant Alerte
Créez le dossier src/Twig/Components/ s'il n'existe pas encore, puis créez-y le fichier Alerte.php.
Ce composant doit avoir :
- Une propriété publique
typede typestring, avec la valeur par défaut'info' - Une propriété publique
messagede typestring, avec la valeur par défaut'' - Une propriété publique
dismissiblede typebool, avec la valeur par défauttrue - Une méthode publique
getCssClass()qui retourne une classe CSS selon le type :'success'→'alert-success''error'→'alert-danger''warning'→'alert-warning'- tout autre type →
'alert-info'
Une solution
Vous devez être connecté pour voir le contenu.
Étape 3 — Créer le template Twig du composant
Créez le fichier templates/components/Alerte.html.twig. Ce template doit :
- Afficher une
<div>avec les classesalertet la classe retournée pargetCssClass() - Afficher le message dans la div
- Si
dismissibleest vrai, afficher un bouton de fermeture<button>avecdata-action="click->alerte#fermer" - Entourer le tout d'un
<div data-controller="alerte">pour permettre à Stimulus d'agir
Une solution
Vous devez être connecté pour voir le contenu.
Étape 4 — Créer le contrôleur Stimulus pour la fermeture
Créez le fichier assets/controllers/alerte_controller.js. Ce contrôleur doit :
- Déclarer une cible
boite - Exposer une méthode
fermer()qui masque l'élément cibleboiteen lui appliquantdisplay: none
Une solution
Vous devez être connecté pour voir le contenu.
Étape 5 — Créer le contrôleur Symfony et la page de démonstration
Générez un contrôleur Symfony nommé AlerteDemoController :
php bin/console make:controller AlerteDemoController
Modifiez src/Controller/AlerteDemoController.php pour passer au template un tableau de messages avec leurs types :
$alertes = [
['type' => 'success', 'message' => 'L\'opération a été réalisée avec succès.'],
['type' => 'error', 'message' => 'Une erreur critique est survenue.'],
['type' => 'warning', 'message' => 'Attention, cette action est irréversible.'],
['type' => 'info', 'message' => 'Une mise à jour est disponible.'],
];
Transmettez ce tableau au template via render().
Une solution
Vous devez être connecté pour voir le contenu.
Étape 6 — Appeler le composant dans le template
Modifiez templates/alerte_demo/index.html.twig pour itérer sur le tableau alertes et appeler le composant <twig:Alerte /> pour chaque entrée. Utilisez le préfixe : pour passer les valeurs dynamiques.
Une solution
Vous devez être connecté pour voir le contenu.
Étape 7 — Tester et vérifier le comportement
Vérifiez d'abord la présence de {{ importmap('app') }} dans templates/base.html.twig, puis lancez le serveur :
symfony server:start
Accédez à https://127.0.0.1:8000/alerte-demo. Vérifiez les points suivants :
- Les quatre alertes s'affichent avec des couleurs différentes selon leur type
- Cliquer sur le bouton
×fait disparaître l'alerte sans rechargement de page - La cinquième alerte (non fermable) n'affiche pas de bouton
× - Ouvrir la console du navigateur (
F12) et vérifier l'absence d'erreurs JavaScript
Une solution
Vous devez être connecté pour voir le contenu.
TP "Slide Symfony UX"
Dans ce TP, vous allez créer un composant Twig réutilisable nommé Slide, capable d'afficher un diaporama de slides navigables grâce à des boutons "Précédent" et "Suivant". La navigation entre les slides sera gérée par un contrôleur Stimulus, sans rechargement de page. Le composant sera ensuite utilisé dans une page de démonstration.
Étape 1 — Créer le projet et installer les dépendances
Ouvrez un terminal PowerShell ou le terminal intégré de VS Code (Ctrl + ù), naviguez vers votre dossier Documents et créez un nouveau projet Symfony :
cd %USERPROFILE%\Documents
symfony new tp-slide-component --webapp
cd tp-slide-component
code .
Une fois VS Code ouvert sur le projet, installez le package ux-twig-component :
composer require symfony/ux-twig-component
Attendez la fin de l'installation, puis vérifiez que le package est bien présent dans composer.json.
Une solution
Vous devez être connecté pour voir le contenu.
Étape 2 — Créer la classe PHP du composant Slide
Créez le fichier src/Twig/Components/Slide.php. Ce composant représente un diaporama. Il doit exposer :
- Une propriété publique
titrede typestring, avec la valeur par défaut'Diaporama' - Une propriété publique
slidesde typearray, initialisée à un tableau vide. Chaque slide sera un tableau associatif avec les cléstitreetcontenu. - Une méthode publique
getNombreSlides()qui retourne le nombre total de slides sous forme d'entier.
Une solution
Vous devez être connecté pour voir le contenu.
Étape 3 — Créer le template Twig du composant
Créez le fichier templates/components/Slide.html.twig. Ce template doit :
- Afficher le titre du diaporama dans un
<h2> - Afficher un compteur de slides (exemple : "Slide 1 / 3") dans un paragraphe utilisant une cible Stimulus
compteur - Itérer sur les slides avec
{% for index, slide in slides %}et afficher chaque slide dans une<div>avecdata-slide-target="panneau" - Masquer par défaut tous les panneaux sauf le premier (via
style="display: none"conditionnel) - Afficher deux boutons de navigation : "Précédent" et "Suivant", chacun avec
data-actionStimulus approprié - Entourer le tout d'un
<div data-controller="slide">
Une solution
Vous devez être connecté pour voir le contenu.
Étape 4 — Créer le contrôleur Stimulus
Créez le fichier assets/controllers/slide_controller.js. Ce contrôleur doit :
- Déclarer les cibles
panneauetcompteur - Déclarer une valeur
totalde typeNumber - Maintenir un index courant (initialisé à
0) dans une propriété d'instance - Exposer une méthode
suivant()qui passe à la slide suivante (sans dépasser la dernière) - Exposer une méthode
precedent()qui revient à la slide précédente (sans passer en dessous de 0) - Chaque changement de slide doit masquer l'ancienne, afficher la nouvelle, et mettre à jour le compteur
Une solution
Vous devez être connecté pour voir le contenu.
Étape 5 — Créer le contrôleur Symfony et préparer les données
Générez un contrôleur Symfony nommé SlideDemoController :
php bin/console make:controller SlideDemoController
Modifiez src/Controller/SlideDemoController.php pour préparer un tableau de slides sur le thème de Symfony UX et le transmettre au template :
$slides = [
['titre' => '...', 'contenu' => '...'],
// au moins 4 slides
];
Chaque slide doit avoir un titre court et un contenu de 1 à 2 phrases.
Une solution
Vous devez être connecté pour voir le contenu.
Étape 6 — Appeler le composant dans le template
Modifiez templates/slide_demo/index.html.twig pour appeler le composant <twig:Slide /> en lui passant le titre du diaporama et le tableau de slides.
Une solution
Vous devez être connecté pour voir le contenu.
Étape 7 — Vérifier la balise importmap et tester
Ouvrez templates/base.html.twig et vérifiez que la balise suivante est bien présente dans le <head> :
{{ importmap('app') }}
Lancez ensuite le serveur de développement et testez le composant dans le navigateur :
symfony server:start
Accédez à https://127.0.0.1:8000/slide-demo et vérifiez :
- La première slide s'affiche au chargement, les autres sont masquées
- Le compteur affiche "Slide 1 / 5"
- Le bouton "Suivant" avance à la slide suivante et met à jour le compteur
- Le bouton "Précédent" revient en arrière
- Les boutons ne dépassent pas les limites (pas de slide 0 ni de slide 6)
- La console du navigateur (
F12) ne contient aucune erreur JavaScript
Une solution
Vous devez être connecté pour voir le contenu.